package org.jflame.web.spring;

import java.util.Map;

import org.jflame.commons.model.BaseResult;
import org.jflame.commons.model.CallResult;
import org.jflame.commons.model.ResultEnum;
import org.jflame.commons.valid.Validators;
import org.jflame.context.spring.SpringContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;

/**
 * Rest全局异常处理
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    final CallResult<Object> ERR;
    final CallResult<Object> PARAM_ERR;

    private boolean enableLocale = false;

    public GlobalExceptionHandler() {
        this(false);
    }

    public GlobalExceptionHandler(boolean enableLocale) {
        this.enableLocale = enableLocale;
        ERR = new CallResult<>(ResultEnum.SERVER_ERROR);
        PARAM_ERR = new CallResult<>(ResultEnum.PARAM_ERROR);
    }

    /**
     * 通用异常处理
     * 
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public CallResult<Object> handle(Exception e, HttpServletRequest request) {
        if (e instanceof BaseResult br) {
            logError(request, e.getMessage());// 有明确业务状态码的不记录异常堆栈
            return br.getStatus() != 0 ? CallResult.Result(br) : CallResult.Error(br.getMessage());
        }
        logError(request, e);
        return enableLocale ? ERR : CallResult.Error(SpringContextHolder.getMessage("jf.server_error"));
    }

    /**
     * 参数绑定校验异常处理
     * 
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = { MethodArgumentNotValidException.class,BindException.class })
    public CallResult<Map<String,String>> handleBindEx(BindException e, HttpServletRequest request) {
        logRequestError(request, e.getLocalizedMessage());
        return SpringWebUtils.convertError(e.getBindingResult());
    }

    /**
     * 参数校验异常处理
     * 
     * @param cve
     * @param request
     * @return
     */
    @ExceptionHandler(value = { ConstraintViolationException.class, })
    public CallResult<Object> handleConstraintViolationEx(ConstraintViolationException cve,
            HttpServletRequest request) {
        String errMsg = Validators.extract(cve);
        logRequestError(request, errMsg);
        return CallResult.ParamError(errMsg);
    }

    /**
     * 参数读取异常处理
     * 
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = { ValidationException.class,ServletRequestBindingException.class,
            TypeMismatchException.class,HttpMessageNotReadableException.class })
    public CallResult<Object> handleParamEx(Exception e, HttpServletRequest request) {
        logRequestError(request, e.getMessage());
        return enableLocale ? PARAM_ERR : CallResult.Error(SpringContextHolder.getMessage("jf.param_error"));
    }

    @ExceptionHandler(value = { HttpRequestMethodNotSupportedException.class })
    public CallResult<Object> handleReqMethodEx(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        logRequestError(request, e.getMessage());
        return new CallResult<Object>(HttpServletResponse.SC_METHOD_NOT_ALLOWED, e.getMessage());
    }

    protected void logRequestError(HttpServletRequest request, String msg) {
        logger.warn("请求异常,url:{}?{}, error: {}", request.getRequestURI(), request.getQueryString(), msg);
    }

    protected void logError(HttpServletRequest request, Object ex) {
        logger.error(String.format("请求处理异常,url:%s?%s, error: %s.", request.getRequestURI(), request.getQueryString(),
                LogFormatUtils.formatValue(ex, false)));
    }

    public void setEnableLocale(boolean enableLocale) {
        this.enableLocale = enableLocale;
    }

}
